#!/usr/bin/env python

# FreeCivRev: a free clone of Sid Meier's Civilization Revolution
#
# Generator for the world map
# Author: Matthew Lockner <mlockner@iastate.edu>

# This is a simplified first cut - create an empty map of all ocean,
# place the icecaps, and then create some 1x1 islands.  We use some
# straightforward heuristics to decide resource placements.

from random import randint, choice, shuffle
from math import hypot
from util import fetch_CSV, populate_type_class, show_build_menu
from util import build_tech_prerequisite_sets

WORLD_MAP_WIDTH = 32	# tiles
WORLD_MAP_HEIGHT = 30	# tiles

MIN_INITIAL_PLAYER_SEPARATION = 5   # radius in tiles

player_start_positions = []

class Terrain_Types:
    pass

class Resource_Types:
    pass

class Building_Types:
    pass
    
class Unit_Types:
    pass
  
class Civilizations:
    pass

buildings_list = fetch_CSV("buildings.csv")
populate_type_class(Building_Types,  buildings_list, "Building")

units_list = fetch_CSV("units.csv")
populate_type_class(Unit_Types, units_list, "Unit")

civilizations_list = fetch_CSV("civilizations.csv")
populate_type_class(Civilizations, civilizations_list, "Civilization")

terrains_list = fetch_CSV("tiles.csv")
populate_type_class(Terrain_Types, terrains_list, "Terrain")

resources_list = fetch_CSV("resources.csv")
populate_type_class(Resource_Types, resources_list, "Resource")

techs_list = fetch_CSV("techs.csv")
build_tech_prerequisite_sets(techs_list)

wonders_list = fetch_CSV("wonders.csv")

# Single-character representations of the terrain types
Terrain_Type_IDs = map(lambda terrain: terrain["ID"], terrains_list)

# Single-character representations of the resource types
Resource_Type_IDs = map(lambda resource: resource["ID"], resources_list)

# Possible 1x1 island terrains
Terrain_Types_1x1_Islands = [ 
    Terrain_Types.Grassland,
    Terrain_Types.Plains,
    Terrain_Types.Forest
]
    
# Resources that can appear in water tiles
Water_Tile_Resource_Types = [
    Resource_Types.Dye, 
    Resource_Types.Fish,
    Resource_Types.Whale
]

#=============================================================================
# Print a textmode representation of the terrain map
def dump_terrain_map():
    for row in terrain_map:
        print ''.join(row)


#=============================================================================
# Get the coordinates of tiles adjacent to that of the argument
# This is a convenience function to handle the horizontal wrapping
def adjacent_tile_coordinates(x, y):
    result = []
    
    for i in range(-1, 2):
        for j in range(-1, 2):
            if i == 0 and j == 0: continue
            result.append(((x+j) % WORLD_MAP_WIDTH, y+i))
            
    return result
    
    
#=============================================================================
# Get the coordinates of tiles at the corners of the tile specified
# This is a convenience function to handle the horizontal wrapping
def corner_tile_coordinates(x, y):
    adjacent_tiles = adjacent_tile_coordinates(x, y)
    
    return [adjacent_tiles[0], adjacent_tiles[2], 
            adjacent_tiles[5], adjacent_tiles[7]]
               
#=============================================================================
# Find a 3x3 block of ocean tiles; return the coordinates of the center tile
# STRONG WARNING: May freeze on an overcrowded map
def random_tile_no_overlap():
    
    Ocean_Type_ID = Terrain_Type_IDs[Terrain_Types.Ocean]

    while True:
        (x, y) = (randint(2, WORLD_MAP_WIDTH - 3), \
                  randint(2, WORLD_MAP_HEIGHT - 3))
                  
        tile_type_IDs = map(
            lambda (x_, y_): terrain_map[y_][x_],
            adjacent_tile_coordinates(x, y)
        )
       
        tile_type_IDs.append(terrain_map[y][x])

        if all(map(lambda c: c == Ocean_Type_ID, tile_type_IDs)):
            return x, y

#=============================================================================
# Place a single 1x1 island on the map with resources
def place_island():

    # Helper function to determine how many resources to place on the corners
    # 1 and 2 is most likely, 0 and 4 are least likely
    def num_corner_resources():
        i = randint(0, 15)
        if i > 7: return 2
        elif i > 2: return 1
        elif i == 2: return 3
        elif i == 1: return 4
        else: return 0
    
    # Pick random tile, avoiding overlap with the icecaps    
    (x, y) = random_tile_no_overlap()
              
    terrain_map[y][x] = Terrain_Type_IDs[choice(Terrain_Types_1x1_Islands)]
    
    # Surround it with shore
    for (x_, y_) in adjacent_tile_coordinates(x, y):
        terrain_map[y_][x_] = Terrain_Type_IDs[Terrain_Types.Shore]
        
    # Place resources
    # FIXME: should be written to a second map layer for resources,
    #        not simply overwritten upon the terrain map
    corners = corner_tile_coordinates(x, y)
    shuffle(corners)

    for i in range(num_corner_resources()):
        (x_, y_) = corners[i]
        terrain_map[y_][x_] = \
            Resource_Type_IDs[choice(Water_Tile_Resource_Types)]

spawn_tile_types = [
    Terrain_Types.Shore,
    Terrain_Types.Grassland,
    Terrain_Types.Plains,
    Terrain_Types.Hill,
    Terrain_Types.Mountain,
    Terrain_Types.Forest,
    Terrain_Types.Desert
]
    
flatland_tile_types = [
    Terrain_Types.Plains,
    Terrain_Types.Hill,
    Terrain_Types.Forest,
    Terrain_Types.Desert,
    Terrain_Types.Grassland
]

#=============================================================================
def place_settler_spawn():
    
    # At minimum the settler must initially be surrounded by two each of
    # water, forest, and food tiles, mod civ-specific bonuses.
    start_tile = choice(flatland_tile_types)
    surrounding_tiles = \
        [Terrain_Types.Forest] * 2 + \
        [Terrain_Types.Grassland] * 2 + \
        [Terrain_Types.Shore] * 2 + \
        [choice(spawn_tile_types), choice(spawn_tile_types)]
    
    shuffle(surrounding_tiles)
    
    surrounding_tile_type_IDs = \
        map(lambda i: Terrain_Type_IDs[i], surrounding_tiles)

    # Settlers will spawn at least a 5-tile radius away from one another

    (x, y) = (0, 0)

    while True:
        (x, y) = random_tile_no_overlap()
        
        distances_to_other_players = \
            map(lambda (x_, y_): hypot(x_ - x, y_ - y), player_start_positions)

        if all(map(lambda dist: dist > MIN_INITIAL_PLAYER_SEPARATION, 
                   distances_to_other_players)):
            break

    adjacent = adjacent_tile_coordinates(x, y)

    terrain_map[y][x] = Terrain_Type_IDs[start_tile]
    for ((x_, y_), t) in zip(adjacent, surrounding_tile_type_IDs):
        terrain_map[y_][x_] = t

    player_start_positions.append((x, y))
    
    
#=============================================================================
# Opening menu: User selects from a list of civs
def pick_civ():
    print "Please select your civilization:"
    print 
    print "Name\t\tLeader"
    print "----\t\t------"
    for i in range(1, len(civilizations_list) + 1):
        civ = civilizations_list[i - 1]
        print str(i) + ". " + civ["Civilization"] + "\t" + civ["Leader"]

    print
    choice = raw_input("Choice [1-" + str(len(civilizations_list)) + "]? ")
    return int(choice)


#=============================================================================
# start

print "*** Welcome to FreeCivRev ***"
player_civ = civilizations_list[pick_civ() - 1]

terrain_map = [[Terrain_Type_IDs[Terrain_Types.Ocean]] * WORLD_MAP_WIDTH \
               for x in range(WORLD_MAP_HEIGHT)]
               
               
# Place icecaps
terrain_map[0] = [Terrain_Type_IDs[Terrain_Types.Glacier]] * WORLD_MAP_WIDTH
terrain_map[WORLD_MAP_HEIGHT - 1] = \
    [Terrain_Type_IDs[Terrain_Types.Glacier]] * WORLD_MAP_WIDTH

# Place 2 - 5 random 1x1 islands
for i in range(randint(2, 5)): place_island()
for i in range(5): place_settler_spawn()
dump_terrain_map()

techs_known = set([])
buildings = set([])
units = set([])
wonders = set([])

while True:
    available_techs = filter(
        lambda x: x["Technology"] not in techs_known,
        filter(
            lambda tech: len(tech["All_Prereq_Techs"] - techs_known) <= 1, 
            techs_list
        )
    )
    
    available_buildings = filter(
        lambda x: (
            x["Prereq_Tech"] in techs_known and
            x["Building"] not in buildings and
            (x["Prereq_Building"] == '' or x["Prereq_Building"] in buildings)
        ),
        buildings_list
    )
    
    available_units = filter(
        lambda x:
            int(x["Cost"]) > 0 and 
            (x["Prereq_Tech"] == '' or x["Prereq_Tech"] in techs_known),
        units_list
    )
    
    available_wonders = filter(
        lambda x:
            x["Endgame"] != "yes" and 
            (x["Prereq_Tech"] == '' or x["Prereq_Tech"] in techs_known),
        wonders_list
    )
        
    print
    print "1.) Research a new technology"
    print "2.) Build Unit"
    print "3.) Build Building"
    print "4.) Build Wonder"
    print "5.) Quit"
    
    choice = int(raw_input("Choice [1-4]? "))
    if choice == 5: break

    if choice == 1:
        print "Techs known: " + ", ".join(techs_known)
        print    
        tech_choice = show_build_menu(available_techs, "Technology")
        if tech_choice == 0: continue
        techs_known.add(available_techs[tech_choice - 1]["Technology"])
        print "Learned " + available_techs[tech_choice - 1]["Technology"]

    elif choice == 2:
        print
        unit_choice = show_build_menu(available_units, "Unit")
        if unit_choice == 0: continue
        units.add(available_units[unit_choice - 1]["Unit"])
        print "Built " + available_units[unit_choice - 1]["Unit"]

    elif choice == 3:
        if len(available_buildings) == 0:
            print "(No buildings available - research technologies to unlock)"
            continue

        print "Buildings built: " + ", ".join(buildings)
        print
        building_choice = show_build_menu(available_buildings, "Building")
        if building_choice == 0: continue
        buildings.add(available_buildings[building_choice - 1]["Building"])
        print "Built " + available_buildings[building_choice - 1]["Building"]

    elif choice == 4:
        print
        wonder_choice = show_build_menu(available_wonders, "Wonder")
        if wonder_choice == 0: continue
        wonders.add(available_units[wonder_choice - 1]["Wonder"])
        print "Built " + available_wonders[wonder_choice -1]["Wonder"]
        
